package top.wyloop.intoeins.module.iam.controller;

import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.stp.StpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import top.wyloop.intoeins.exception.SystemException;
import top.wyloop.intoeins.module.common.service.CaptchaService;
import top.wyloop.intoeins.module.iam.model.consts.AuthorityConst;
import top.wyloop.intoeins.module.iam.model.consts.CacheControlConst;
import top.wyloop.intoeins.module.iam.model.consts.InitUserConst;
import top.wyloop.intoeins.module.iam.model.entity.User;
import top.wyloop.intoeins.module.iam.model.form.BindForm;
import top.wyloop.intoeins.module.iam.model.form.ChangePasswordForm;
import top.wyloop.intoeins.module.iam.model.form.UserCondition;
import top.wyloop.intoeins.module.iam.model.form.UserForm;
import top.wyloop.intoeins.module.iam.service.PermissionService;
import top.wyloop.intoeins.module.iam.service.RoleService;
import top.wyloop.intoeins.module.iam.service.UserService;
import top.wyloop.intoeins.response.Map;
import top.wyloop.intoeins.response.Response;

import java.util.List;

/**
 * @author Wyatt6
 * @date 2025/1/23
 */
@Slf4j
@RestController
@RequestMapping("/api/iam/user")
public class UserController {
    @Autowired
    private CaptchaService captchaService;
    @Autowired
    private UserService userService;
    @Autowired
    private RoleService roleService;
    @Autowired
    private PermissionService permissionService;

    /**
     * 注册新用户
     *
     * @param input 注册信息
     */
    @PostMapping("/register")
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public Response register(@RequestBody UserForm input) {
        log.info("[[注册新用户]]");
        Assert.notNull(input, "注册信息为空");
        Assert.hasText(input.getCaptchaKey(), "验证码KEY为空");
        Assert.hasText(input.getCaptcha(), "验证码为空");
        Assert.hasText(input.getUsername(), "用户名为空");
        Assert.hasText(input.getPassword(), "密码为空");

        String captchaKey = input.getCaptchaKey();
        String captcha = input.getCaptcha();
        String username = input.getUsername();
        String password = input.getPassword();
        log.info("请求参数: captchaKey=[{}], captcha=[{}], username=[{}]", captchaKey, captcha, username);

        captchaService.verifyCaptcha(captchaKey, captcha);
        if (!userService.checkUsernameFormat(username)) {
            throw new SystemException("用户名格式错误");
        }
        if (!userService.checkPasswordFormat(password)) {
            throw new SystemException("密码格式错误");
        }
        User user = userService.createUser(username, password);

        return new Response().success();
    }

    /**
     * 用户登录
     *
     * @param input 登录信息
     * @return {user 用户对象, token 令牌, tokenExpiredTime 令牌过期时间}
     */
    @PostMapping("/login")
    public Response login(@RequestBody UserForm input) {
        log.info("[[用户登录]]");
        Assert.notNull(input, "登录信息为空");
        Assert.hasText(input.getCaptchaKey(), "验证码KEY为空");
        Assert.hasText(input.getCaptcha(), "验证码为空");
        Assert.hasText(input.getUsername(), "用户名为空");
        Assert.hasText(input.getPassword(), "密码为空");

        String captchaKey = input.getCaptchaKey();
        String captcha = input.getCaptcha();
        String username = input.getUsername();
        String password = input.getPassword();
        log.info("请求参数: captchaKey=[{}], captcha=[{}], username=[{}]", captchaKey, captcha, username);

        captchaService.verifyCaptcha(captchaKey, captcha);

        if (!userService.checkUsernameFormat(username)) {
            throw new SystemException("用户名格式错误");
        }
        if (!userService.checkPasswordFormat(password)) {
            throw new SystemException("密码格式错误");
        }

        User user = userService.authentication(username, password);

        log.info("登录：Sa-Token框架自动生成token，并缓存到Redis");
        StpUtil.login(user.getId());
        log.info("loginId=[{}]", StpUtil.getLoginId());
        log.info("token=[{}]", StpUtil.getTokenValue());
        log.info("sessionId=[{}]", StpUtil.getSession().getId());

        user = userService.updateLastLoginTime(user.getId());
        log.info("用户数据脱敏");
        user.setPassword(null);
        user.setSalt(null);

        return new Response().success()
                .addData("user", user)
                .addData("token", StpUtil.getTokenValue())
                .addData("tokenExpiredTime", System.currentTimeMillis() + StpUtil.getTokenTimeout() * 1000);
    }

    /**
     * 退出登录
     */
    @GetMapping("/logout")
    public Response logout() {
        log.info("[[退出登录]]");
        log.info("token=[{}]", StpUtil.getTokenValue());
        log.info("loginId=[{}]", StpUtil.getLoginIdAsLong());
        StpUtil.logout();
        log.info("退出登录成功");
        return new Response().success();
    }

    /**
     * 修改密码
     *
     * @param input 新、旧密码
     */
    @SaCheckLogin
    @PostMapping("/changePassword")
    public Response changePassword(@RequestBody ChangePasswordForm input) {
        log.info("[[修改密码]]");
        Assert.notNull(input, "输入数据为空");
        Assert.hasText(input.getOldPassword(), "旧密码为空");
        Assert.hasText(input.getNewPassword(), "新密码为空");

        log.info("检查新、旧密码格式");
        if (!userService.checkPasswordFormat(input.getOldPassword())) {
            throw new SystemException("旧密码格式错误");
        }
        if (!userService.checkPasswordFormat(input.getNewPassword())) {
            throw new SystemException("新密码格式错误");
        }

        log.info("loginId=[{}]", StpUtil.getLoginId());
        userService.changePassword(StpUtil.getLoginIdAsLong(), input.getNewPassword());

        return new Response().success();
    }

    /**
     * 冻结用户
     *
     * @param userId 要冻结的用户ID
     */
    @SaCheckLogin
    @SaCheckPermission(AuthorityConst.USER_EDIT)
    @GetMapping("/freeze")
    public Response freeze(@RequestParam("userId") Long userId) {
        log.info("[[冻结用户]]");
        Assert.notNull(userId, "用户ID为空");
        log.info("请求参数：userId=[{}]", userId);

        if (userId.equals(InitUserConst.SUPER_ADMIN_USER_ID)) {
            throw new SystemException("无法冻结超级管理员用户");
        }
        userService.setActivated(userId, false);
        log.info("退出被冻结用户的登录状态");
        StpUtil.logout(userId);

        return new Response().success();
    }

    /**
     * 解冻用户
     *
     * @param userId 要解冻的用户ID
     */
    @SaCheckLogin
    @SaCheckPermission(AuthorityConst.USER_EDIT)
    @GetMapping("/unfreeze")
    public Response unfreeze(@RequestParam("userId") Long userId) {
        log.info("[[解冻用户]]");
        Assert.notNull(userId, "用户ID为空");
        log.info("请求参数：userId=[{}]", userId);

        userService.setActivated(userId, true);

        return new Response().success();
    }

    /**
     * 获取用户的授权数据（角色+权限）
     *
     * @return authority 授权数据
     */
    @SaCheckLogin
    @GetMapping("/getAuthority")
    public Response getAuthority() {
        log.info("[[获取用户的授权数据（角色+权限）]]");
        Long userId = StpUtil.getLoginIdAsLong();
        List<String> roles = roleService.getActivatedRoleIdentifiers(userId);
        List<String> permissions = permissionService.getActivatedPermissionIdentifiers(userId);
        Map authority = new Map();
        authority.put("roles", roles);
        authority.put("permissions", permissions);
        return new Response().success().addData("authority", authority);
    }

    /**
     * 刷新用户的授权数据（角色+权限）
     *
     * @return authority 授权数据
     */
    @SaCheckLogin
    @GetMapping("/updateAuthority")
    public Response updateAuthority() {
        log.info("[[刷新用户的授权数据（角色+权限）]]");

        Long userId = StpUtil.getLoginIdAsLong();
        log.info("userId=[{}]", userId);

        log.info("删除该用户缓存的授权数据");
        try {
            StpUtil.getSessionByLoginId(userId).delete(CacheControlConst.USER_ROLES_CACHE_KEY);
            StpUtil.getSessionByLoginId(userId).delete(CacheControlConst.USER_PERMISSIONS_CACHE_REFRESHED);
        } catch (Exception e) {
            e.printStackTrace();
        }

        log.info("重新查询该用户缓存的授权数据");
        List<String> roles = roleService.getActivatedRoleIdentifiers(userId);
        List<String> permissions = permissionService.getActivatedPermissionIdentifiers(userId);
        Map authority = new Map();
        authority.put("roles", roles);
        authority.put("permissions", permissions);
        return new Response().success().addData("authority", authority);
    }

    /**
     * （条件）查询用户分页
     *
     * @param pageNum   页码
     * @param pageSize  页大小
     * @param condition 查询条件
     * @return {pageNum 页码, pageSize 页大小, totalPages 页码总数, totalElements 总条数, users 用户分页}
     */
    @SaCheckLogin
    @SaCheckPermission(AuthorityConst.USER_QUERY)
    @PostMapping("/getUsersByPage")
    public Response getUsersByPage(@RequestParam("pageNum") int pageNum,
                                   @RequestParam("pageSize") int pageSize,
                                   @RequestBody(required = false) UserCondition condition) {
        log.info("[[（条件）查询用户分页]]");
        log.info("请求参数：pageNum=[{}], pageSize=[{}], condition=[{}]", pageNum, pageSize, condition);

        log.info("调用条件查询用户分页服务");
        Page<User> users = userService.getUsersByPageDataMaskConditionally(
                pageNum,
                pageSize,
                (condition == null || condition.getUsername() == null) ? null : condition.getUsername(),
                (condition == null || condition.getActivated() == null) ? null : condition.getActivated(),
                (condition == null || condition.getStCreateTime() == null) ? null : condition.getStCreateTime(),
                (condition == null || condition.getEdCreateTime() == null) ? null : condition.getEdCreateTime(),
                (condition == null || condition.getRole() == null) ? null : condition.getRole(),
                (condition == null || condition.getStLastLoginTime() == null) ? null : condition.getStLastLoginTime(),
                (condition == null || condition.getEdLastLoginTime() == null) ? null : condition.getEdLastLoginTime());

        return new Response().success()
                .addData("pageNum", users.getPageable().getPageNumber())
                .addData("pageSize", users.getPageable().getPageSize())
                .addData("totalPages", users.getTotalPages())
                .addData("totalElements", users.getTotalElements())
                .addData("users", users.getContent());
    }

    /**
     * 更新用户绑定的角色
     *
     * @param input { userId 用户ID, bind 绑定ID列表, cancel 取消绑定ID列表 }
     * @return { failBind 绑定失败ID列表, failCancel 取消绑定失败ID列表 }
     */
    @SaCheckLogin
    @SaCheckPermission(AuthorityConst.UPDATE_BINDS)
    @PostMapping("/updateBinds")
    public Response updateBinds(@RequestBody BindForm input) {
        log.info("[[更新用户绑定的角色]]");
        Assert.notNull(input, "绑定/取消绑定表单为空");
        log.info("请求参数: {}", input);

        List<Long> failBind = null;
        List<Long> failCancel = null;
        try {
            log.info("绑定");
            failBind = userService.bind(input.getUserId(), input.getBind());
        } catch (IllegalArgumentException e) {
            log.info("无须绑定");
        }
        try {
            log.info("解除绑定");
            failCancel = userService.cancelBind(input.getUserId(), input.getCancel());
        } catch (IllegalArgumentException e) {
            log.info("无须解除绑定");
        }

        return new Response().success()
                .addData("failBind", failBind)
                .addData("failCancel", failCancel);
    }
}
